package top.winshu.common.util;

import org.apache.commons.lang3.time.DateFormatUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.util.*;

/**
 * @author winshu
 * @since 2020/10/12
 */
public final class DateUtils {

    private DateUtils() {
    }

    public static final String YYYY_MM_DD = "yyyy-MM-dd";
    public static final String YYYYMMDD = "yyyyMMdd";
    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";

    private static final String COMMON_PATTERN = "[yyyyMMddHHmmss][yyyyMMddHHmm][yyyyMMddHH][yyyyMMdd][yyyyMM][yyyy][[-][/][.]MM][[-][/][.]dd][[-][/][.]M][[-][/][.]d][[-][/][.]MM][[-][/][.]d][[-][/][.]M][[-][/][.]dd][ ][HH][H][[:][.]mm][[:][.]m][[:][.]ss][[:][.]s][[:][.]SSS]";

    private static final DateTimeFormatter FORMATTER_YYYY_MM_DD = DateTimeFormatter.ofPattern(YYYY_MM_DD);
    private static final DateTimeFormatter FORMATTER_YYYYMMDDHHMMSS = DateTimeFormatter.ofPattern(YYYYMMDDHHMMSS);
    private static final DateTimeFormatter FORMATTER_YYYYMMDD = DateTimeFormatter.ofPattern(YYYYMMDD);
    private static final DateTimeFormatter FORMATTER_YYYY_MM_DD_HH_MM_SS = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS);
    private static final DateTimeFormatter FORMATTER_COMMON = new DateTimeFormatterBuilder()
            .parseCaseSensitive()
            .appendPattern(COMMON_PATTERN)
            .parseDefaulting(ChronoField.YEAR_OF_ERA, LocalDateTime.now().getYear())
            .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
            .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
            .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
            .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
            .parseDefaulting(ChronoField.NANO_OF_SECOND, 0)
            .toFormatter();

    private static final ZoneId DEFAULT_ZONE = ZoneId.systemDefault();

    /**
     * 当前时间
     */
    public static Date now() {
        return new Date();
    }

    /**
     * 获取今天的日期， 不含时分秒
     */
    public static Date today() {
        LocalDate localDate = LocalDate.now();
        return Date.from(localDate.atTime(LocalTime.MIN).atZone(DEFAULT_ZONE).toInstant());
    }

    /**
     * 获取昨天的日期，不含时分秒
     */
    public static Date yesterday() {
        LocalDate localDate = LocalDate.now().minusDays(1);
        return Date.from(localDate.atTime(LocalTime.MIN).atZone(DEFAULT_ZONE).toInstant());
    }

    /**
     * @see #YYYY_MM_DD
     */
    public static String formatDate(Date date) {
        return formatDate(date, YYYY_MM_DD);
    }

    /**
     * @see #YYYY_MM_DD_HH_MM_SS
     */
    public static String formatDateTime(Date date) {
        return formatDate(date, YYYY_MM_DD_HH_MM_SS);
    }

    /**
     * 按指定方式格式化日期
     */
    public static String formatDate(Date date, String pattern) {
        return DateFormatUtils.format(date, pattern);
    }

    /**
     * 按 HH:mm:ss 转日期
     */
    public static Date toTime(String time) {
        LocalTime localTime = LocalTime.parse(time, DateTimeFormatter.ISO_TIME);
        return Date.from(localTime.atDate(LocalDate.now()).atZone(DEFAULT_ZONE).toInstant());
    }

    /**
     * 通用的日期解析方法，支持常见的所有日期/时间格式
     * <p>
     * 建议优先使用其他转换方法
     *
     * @throws DateTimeParseException 解析失败时抛出
     */
    public static Date toDate(String date) throws DateTimeParseException {
        return toDateTime(date, FORMATTER_COMMON);
    }

    /**
     * @throws DateTimeParseException 解析失败时抛出
     * @see #YYYY_MM_DD
     */
    public static Date toDate1(String date) throws DateTimeParseException {
        return toDate(date, FORMATTER_YYYY_MM_DD);
    }

    /**
     * @throws DateTimeParseException 解析失败时抛出
     * @see #YYYYMMDD
     */
    public static Date toDate2(String date) throws DateTimeParseException {
        return toDate(date, FORMATTER_YYYYMMDD);
    }

    /**
     * @throws DateTimeParseException 解析失败时抛出
     * @see #YYYY_MM_DD_HH_MM_SS
     */
    public static Date toDateTime(String date) throws DateTimeParseException {
        return toDateTime(date, FORMATTER_YYYY_MM_DD_HH_MM_SS);
    }

    public static Date toDateTime2(String date) throws DateTimeParseException {
        return toDateTime(date, FORMATTER_YYYYMMDDHHMMSS);
    }

    /**
     * 解析日期，不含时间
     *
     * @throws DateTimeParseException 解析失败时抛出
     */
    private static Date toDate(String string, DateTimeFormatter formatter) throws DateTimeParseException {
        if (string == null || string.length() == 0) {
            return null;
        }
        LocalDate localDate = LocalDate.parse(string, formatter);
        return Date.from(localDate.atTime(LocalTime.MIN).atZone(DEFAULT_ZONE).toInstant());
    }

    /**
     * 解析日期时间
     *
     * @throws DateTimeParseException 解析失败时抛出
     */
    private static Date toDateTime(String string, DateTimeFormatter formatter) throws DateTimeParseException {
        if (string == null || string.length() == 0) {
            return null;
        }
        LocalDateTime localDateTime = LocalDateTime.parse(string, formatter);
        return Date.from(localDateTime.atZone(DEFAULT_ZONE).toInstant());
    }

    /**
     * 截断日期，去掉时分秒
     */
    public static Date trunc(Date date) {
        if (date == null) {
            return null;
        }
        LocalDate localDate = date.toInstant().atZone(DEFAULT_ZONE).toLocalDate();
        return Date.from(localDate.atTime(LocalTime.MIN).atZone(DEFAULT_ZONE).toInstant());
    }

    /**
     * 获取日期开始时间
     */
    public static Date getDateStart(Date date) {
        return date == null ? null : trunc(date);
    }

    /**
     * 获取日期结束时间
     */
    public static Date getDateEnd(Date date) {
        if (date == null) {
            return null;
        }
        LocalDate localDate = date.toInstant().atZone(DEFAULT_ZONE).toLocalDate();
        return Date.from(localDate.atTime(LocalTime.MAX).atZone(DEFAULT_ZONE).toInstant());
    }

    public static boolean isSameDay(Date d1, Date d2) {
        if (null == d1 || null == d2) {
            return false;
        }
        Calendar cal1 = Calendar.getInstance();
        cal1.setTime(d1);
        Calendar cal2 = Calendar.getInstance();
        cal2.setTime(d2);

        return cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA)
                && cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
                && cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
    }

    /**
     * 列出指定日期区间的每一天
     */
    public static List<Date> listDateRange(Date startDate, Date endDate) {
        List<Date> dates = new ArrayList<>();

        Calendar calendarStart = Calendar.getInstance();
        calendarStart.setTime(startDate);

        Calendar calendarEnd = Calendar.getInstance();
        calendarEnd.setTime(endDate);

        while (endDate.compareTo(calendarStart.getTime()) >= 0) {
            dates.add(calendarStart.getTime());
            calendarStart.add(Calendar.DAY_OF_MONTH, 1);
        }
        return dates;
    }

    /**
     * 列出指定日期的前 n 天，不包括该日期
     */
    public static List<Date> listBeforeNDay(Date startDate, int n) {
        List<Date> dates = new ArrayList<>();

        Calendar calendarStart = Calendar.getInstance();
        calendarStart.setTime(startDate);

        for (int i = 1; i <= n; i++) {
            calendarStart.add(Calendar.DAY_OF_MONTH, -1);
            dates.add(calendarStart.getTime());
        }
        Collections.reverse(dates);
        return dates;
    }

    /**
     * @param delta 获取基准日期N天前的日期
     * @param date  基准日期
     */
    public static Date getDateBeforeNDays(Date date, int delta) {
        if (delta < 0) {
            throw new IllegalArgumentException("param delta must greater than 0");
        }
        return addDay(date, -delta);
    }

    /**
     * @param delta 获取基准日期N月前的日期
     * @param date  基准日期
     */
    public static Date getDateBeforeNMonths(Date date, int delta) {
        if (delta < 0) {
            throw new IllegalArgumentException("param delta must greater than 0");
        }
        return addMonth(date, -delta);
    }

    /**
     * 计算两个时间相差秒数的绝对值
     */
    public static int getDistanceAsSeconds(Date before, Date after) {
        long beforeTime = before.getTime();
        long afterTime = after.getTime();
        return Math.abs((int) ((afterTime - beforeTime) / 1000));
    }

    /**
     * 设置天
     */
    public static Date addDay(Date date, int delta) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, delta);
        return calendar.getTime();
    }

    /**
     * 设置月
     */
    public static Date addMonth(Date date, int delta) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MONTH, delta);
        return calendar.getTime();
    }

    /**
     * 计算两个日期间的天数，返回值一定是正数
     */
    public static int getDays(Date date1, Date date2) {
        return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24)));
    }

    /**
     * 获取周的第一天
     *
     * @param date 日期
     * @param n    -1上周，0本周，1下周，2下下周，依次类推
     */
    public static Date getWeekFirstDay(Date date, int n) {
        int week = 7;
        Calendar cal = Calendar.getInstance();
        // 设置一个星期的第一天，按中国的习惯一个星期的第一天是星期一
        cal.setFirstDayOfWeek(Calendar.MONDAY);
        cal.setTime(date);
        cal.add(Calendar.DATE, n * week);
        cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
        return cal.getTime();
    }

    /**
     * 获取月的第一天
     *
     * @param date 日期
     * @param n    -1上月，0本月，1下月，2下下月，依次类推
     */
    public static Date getMonthFirstDay(Date date, int n) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MONTH, n);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        return calendar.getTime();
    }

    /**
     * 获取月的最后一天
     *
     * @param date 日期
     * @param n    -1上月，0本月，1下月，2下下月，依次类推
     */
    public static Date getMonthLastDay(Date date, int n) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MONTH, n);
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        return calendar.getTime();
    }

    public static void main(String[] args) {
        System.out.println(DateUtils.formatDate(getMonthFirstDay(new Date(), 1)));
        System.out.println(DateUtils.formatDate(getMonthLastDay(new Date(), 1)));
    }

}
